import re
import json
import requests
import subprocess
from collections import defaultdict

# 常量配置
API_URL = "https://mes.toasin.cn/check/index/serverSnBomInfo"
MEMORY_JSON = "/tmp/memory.json"
DISK_JSON = "/tmp/disk.lshw.json"
REPORT_DIR = "/tmp"
STONE_LOG = "/tmp/stone-check.log"

KEYWORDS = {
    'ECC': 'ECC|Memory|DIMM',
    'PSU': 'Power Supply|PSU|Voltage|AC lost',
    'TEMP': 'Temperature|Thermal',
    'FAN': 'Fan|Cooling',
    'CRITICAL': 'Critical|Fatal|Error'
}

FILTERS = [
    'Timestamp Clock Sync',
    'Pre-Init',
    '^$'
]

def bash(cmd: str) -> str:
    """
    执行Shell命令并返回输出结果。
    """
    try:
        return subprocess.check_output(cmd, shell=True, executable='/bin/bash', stderr=subprocess.STDOUT).decode().strip()
    except subprocess.CalledProcessError as e:
        return e.output.decode().strip()

def get_sn() -> str:
    """
    获取服务器序列号。
    """
    server_sn = bash("dmidecode -t system | grep 'Serial Number' | awk -F: '{print $2}'|sed 's/ //g'")
    if server_sn == "ToBeFilledByO.E.M.":
        server_sn = bash("ipmitool fru|grep Serial|awk -F: '{print $2}'|sed 's/ //g'")
    return server_sn

def save_log(content: str, filename: str):
    """
    保存日志文件。
    """
    with open(filename, 'w') as f:
        f.write(content)

def json_load(json_file):
    """
    加载JSON文件并返回Python对象。
    """
    with open(json_file, 'r') as f:
        return json.load(f)

def convert_capacity(cap):
    cap = cap.strip().upper()
    if 'G' in cap:
        return float(cap.replace('G', ''))
    elif 'T' in cap:
        return float(cap.replace('T', '')) * 1000
    else:
        try:
            return float(cap)
        except ValueError:
            return 0.0

def convert_size_to_gb(size_in_bytes):
    return size_in_bytes / (1000 ** 3)

def format_capacity(size_gb):
    if size_gb < 1000:
        return f"{int(size_gb)}G"
    else:
        return f"{size_gb / 1000:.2f}T"

def parse_inventory(api_url, sn):
    """
    从API获取server配置，并分类整理成inventory字典。
    """
    data = {"server_sn": sn}
    try:
        response = requests.post(api_url, data=data)
        response.raise_for_status()
        response_data = response.json().get("data", [])
    except requests.RequestException as e:
        print(f"API请求错误: {e}")
        response_data = None

    if response_data is None or not isinstance(response_data, list):
        return {}

    inventory = {
        "CPU": [], "RAM": [], "DISK": [],
        "NIC": [], "RAID": [], "GPU": []
    }

    keep_fields = ['brand', 'model', 'sku', 'total', 'capacity', 'frequency']

    for item in response_data:
        cate = item.get("cate", "")
        filtered_item = {field: item.get(field, '') for field in keep_fields}
        if cate in ["OFC", "OCP"]:
            inventory["NIC"].append(filtered_item)
        elif cate in ["SSD", "HDD"]:
            inventory["DISK"].append(filtered_item)
        elif cate in inventory:
            inventory[cate].append(filtered_item)
    return inventory

def parse_cpu(api_info):
    model = bash("lscpu|grep -Ei 'model name'")
    socket = int(bash('cat /proc/cpuinfo| grep "physical id"| sort| uniq| wc -l'))
    if api_info and 'total' in api_info[0] and 'model' in api_info[0]:
        if int(api_info[0]['total']) == socket and api_info[0]['model'].lower() in model.lower():
            return ""
    return "CPU检测失败;"

def parse_mem(mem_data, ram_info):
    mem_result = {"vendor": "", "product": "", "size": "", "clock": "", "total": 0, "status": ""}
    for item in mem_data:
        item['product'] = "M393A4K40EB3-CWE"#TODO 测试代码，需删除
        if item.get('class') == "memory" and 'serial' in item:
            mem_result['total'] += 1
            if mem_result['product'] == "":
                mem_result['vendor'] = item.get('vendor', "")
                mem_result['product'] = item.get('product', "")
                size_val = item.get('size', 0)
                clock_val = item.get('clock', 0)
                if size_val and clock_val:
                    mem_result['size'] = float(size_val / 1024 / 1024 / 1024)
                    mem_result['clock'] = int(clock_val) / 1000 / 1000
            else:
                if mem_result['product'] != item.get('product', ""):
                    if "PN不一致;" not in mem_result['status'] and len(ram_info) == 1:
                        mem_result['status'] += "内存PN不一致;"

    total_sum = sum(item.get('total', 0) for item in ram_info)
    if mem_result['total'] != total_sum:
        mem_result['status'] += "内存数量不一致;"

    return mem_result['status']

def parse_disk(disk_data, disk_info):
    total_sum = sum(item.get('total', 0) for item in disk_info)
    disk_sum = len(disk_data)
    if total_sum != disk_sum:
        return "硬盘数量不一致;"

    actual_map = {}
    for item in disk_data:
        product = item.get('product', '')
        size_val = item.get('size', 0)
        size_gb = convert_size_to_gb(size_val)
        if product in actual_map:
            actual_map[product]['total'] += 1
        else:
            actual_map[product] = {'size': size_gb, 'total': 1}

    actual_list = [f"{format_capacity(info['size'])}*{info['total']}" for info in actual_map.values()]
    actual_list.sort(key=lambda x: convert_capacity(x.split('*')[0]))

    expected_list = [f"{item['capacity']}*{item['total']}" for item in disk_info]
    expected_list.sort(key=lambda x: convert_capacity(x.split('*')[0]))

    for actual, expected in zip(actual_list, expected_list):
        a_cap, a_tot = actual.split('*')
        e_cap, e_tot = expected.split('*')

        a_cap_val = convert_capacity(a_cap)
        e_cap_val = convert_capacity(e_cap)

        if e_cap_val == 0:
            continue

        if a_tot != e_tot or not (0.95 * e_cap_val <= a_cap_val <= 1.05 * e_cap_val):
            return "硬盘容量不匹配;"

    return ""

def parse_gpu(gpu_info):
    if gpu_info and 'brand' in gpu_info[0] and "nvidia" in (gpu_info[0]['brand']).lower():
        gpu_total = int(bash("lspci -d 10de: -s *.0|wc -l"))
        if gpu_total != int(gpu_info[0]['total']):
            return "GPU数量不一致;"
    return ""

def parse_nic(nic_info):
    api_total = len(nic_info)
    nic_total = int(bash("lspci -s *.0|grep -Ei 'Ethernet|InfiniBand'|wc -l"))
    if nic_total == api_total or nic_total == api_total + 1:
        return ""
    else:
        return "网卡数量不一致;"

def get_api():
    sn = get_sn()
    inventory = parse_inventory(API_URL, sn)
    return inventory

def check_sys(api_info):
    cpu_res = parse_cpu(api_info['CPU'])
    mem_res = parse_mem(json_load(MEMORY_JSON), api_info['RAM'])
    disk_res = parse_disk(json_load(DISK_JSON), api_info['DISK'])

    gpu_res = ""
    nic_res = ""
    if api_info['GPU']:
        gpu_res = parse_gpu(api_info['GPU'])
    if api_info['NIC']:
        nic_res = parse_nic(api_info['NIC'])

    return mem_res + cpu_res + disk_res + gpu_res + nic_res

def get_system_info():
    return {
        'Chassis & Device Info': bash("dmidecode -t 1"),
        'CPU': bash("lscpu|grep -Ev 'Vulner|Flags'"),
        'Memory': bash("""dmidecode -t 17 | perl -ne 'BEGIN{$/=""} 
            if (/Size:\s*(\d+\s*MB)?/ && !/No Module/) {
                @fields = (/(?:Size|Locator|Speed|Manufacturer|Serial Number|Part Number):\s*(.+?)(?=\n|$)/g);
                print join("\t", map{s/^\s+|\s+$//gr} @fields), "\n"
            }'"""),
        'Disk': bash("lsblk -o NAME,FSTYPE,LABEL,MOUNTPOINT,SIZE,MODEL,SERIAL,STATE,VENDOR"),
        'PCI': bash("lspci |grep -Ei 'sas|raid|eth|infi|nvidia'"),
        'BMC - FRU': bash("ipmitool fru print"),
        'BMC - LAN': bash("ipmitool lan print 1"),
        'BMC - User': bash("ipmitool user list 1 | grep -Evi 'NO ACCESS'")
    }

def smart_disk_check():
    smart_info = bash('for i in `ls /dev/ | grep -E "^sd[a-z]+$|^nvme[0-9]+n1$"`; do text=`smartctl -H /dev/$i | grep "result"`;echo "/dev/$i: $text" ;done')
    if 'FAILED' in smart_info.upper():
        return {'status': 'FAIL', 'details': smart_info}
    else:
        return {'status': 'PASS', 'details': smart_info}

def log_filter(content):
    lines = [line for line in content.split('\n') if line.strip()
             and not any(re.search(pat, line, re.IGNORECASE) for pat in FILTERS)]
    results = defaultdict(list)
    remaining = []
    for line in lines:
        matched = False
        for category, pattern in KEYWORDS.items():
            if re.search(pattern, line, re.IGNORECASE):
                results[category].append(line)
                matched = True
                break
        if not matched:
            remaining.append(line)
    if remaining:
        results['Other'] = remaining
    return dict(results)

def log_handler():
    server_sn = get_sn()
    sel_info = bash("ipmitool sel elist")
    save_log(bash("dmesg"), f"{REPORT_DIR}/dmesg.log")
    save_log(sel_info, f"{REPORT_DIR}/sel.log")
    save_log(bash("ipmitool sdr"), f"{REPORT_DIR}/sdr.log")

    filtered_logs = {
        'DMESG': log_filter(
            bash("dmesg --level emerg,alert,err | grep -Ev 'overriding NVDATA|Failed to start|SGX disabled'")
        ),
        'SEL': log_filter(sel_info),
        'SDR': log_filter(bash("ipmitool sdr|grep -Ev 'ok|ns'"))
    }
    stone_log_data = bash(f"cat {STONE_LOG}")
    smart_res = smart_disk_check()
    return {
        'SerialNumber': server_sn,
        'System_Info': get_system_info(),
        'filterLog': filtered_logs,
        'STONE_Check': stone_log_data,
        'SMART_Check': smart_res
    }

def log_checker(results):
    test_stat = {
        'STONE_Check': 'FAIL' if 'FAIL' in results['STONE_Check'] else 'PASS',
        'DMESG': 'PASS' if not results['filterLog']['DMESG'] else 'WARN',
        'SEL': ('FAIL' if 'CRITICAL' in results['filterLog']['SEL'] else
                'PASS' if len(results['filterLog']['SEL']) == 1 and 'Other' in results['filterLog']['SEL'] 
                else 'WARN'),
        'SDR': 'PASS' if not results['filterLog']['SDR'] else 'WARN',
        'SMART': results['SMART_Check']['status']
    }

    if 'FAIL' in test_stat.values():
        final_res = 'FAIL'
    elif 'WARN' in test_stat.values():
        final_res = 'WARN'
    else:
        final_res = 'PASS'

    return test_stat, final_res

def generate_report(results, test_stat, final_res):
    report_file = f"{REPORT_DIR}/report_{results['SerialNumber']}.log"
    content = []

    content.append(f"服务器序列号: {results['SerialNumber']}\n\n")
    content.append("=== 系统详细信息 ===\n")
    for key, value in results['System_Info'].items():
        content.append(f"{key}:\n{value}\n\n")

    content.append("=== 日志分析 ===\n")
    for item in ['DMESG', 'SEL', 'SDR']:
        content.append(f"{item} 日志分析:\n")
        if results['filterLog'][item]:
            for cate, events in results['filterLog'][item].items():
                content.append(f"{cate}: {len(events)} events\n")
                if cate != 'Other':
                    for event in events:
                        content.append(f"  - {event}\n")
        else:
            content.append("No abnormal events\n")
        content.append("\n")

    content.append("=== MES压测日志 ===\n")
    content.append(results['STONE_Check'] + "\n\n")

    content.append("=== 磁盘 SMART 状态 ===\n")
    content.append(f"检查结果: {results['SMART_Check']['status']}\n")
    content.append("详细信息:\n")
    content.append(results['SMART_Check']['details'] + "\n\n")

    content.append("=== 测试状态 ===\n")
    for key, value in test_stat.items():
        content.append(f"{key}: {value}\n")
    content.append(f"\n总体测试结果: {final_res}")

    with open(report_file, 'w') as f:
        f.writelines(content)
    return report_file

def check():
    api_info = get_api()
    if not api_info or not all(key in api_info for key in ['CPU', 'RAM', 'DISK']):
        return 0
    result = check_sys(api_info)
    if result == "":
        return 1
    else:
        return -1

def report():
    results = log_handler()
    test_stat, final_res = log_checker(results)
    report_path = generate_report(results, test_stat, final_res)
    print("\n=== 服务器健康检查概要 ===")
    print(f"服务器序列号: {results['SerialNumber']}")
    print(f"总体测试结果: {final_res}")
    bash("/root/scripts/stone-server-check/src/client/scripts/upload.sh")
